#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (C) 2021 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
import argparse
import logging
from ftrace_format_parser import FtraceEventCodeGenerator
from ftrace_format_parser import ProtoType

AUTO_GENERATED_GNI = 'autogenerated.gni'

THIS_FILE = os.path.basename(__file__)
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
    level=logging.INFO)
logger = logging.getLogger(THIS_FILE)

CPP_COPYRIGHT_HEADER = '''\
/* THIS FILE IS GENERATE BY {}, PLEASE DON'T EDIT IT!
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'''.format(THIS_FILE)

GN_COPYRIGHT_HEADER = '''\
# THIS FILE IS GENERATE BY {}, PLEASE DON'T EDIT IT!
# Copyright (C) 2021 Huawei Device Co., Ltd.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
'''.format(THIS_FILE)

PARSE_FUNCTION_ARGS = '({}, {}, {}, {})'.format('FtraceEvent& ftraceEvent',
    'uint8_t data[]', 'size_t size', 'const EventFormat& format')
PARSE_REGISTER_MACRO = 'REGISTER_FTRACE_EVENT_PARSE_FUNCTION'

CHECK_FUNCTION_ARGS = '(const FtraceEvent& event) -> bool'
FORMAT_FUNCTION_ARGS = '(const FtraceEvent& event) -> std::string'
FORMAT_REGISTER_MACRO = 'REGISTER_FTRACE_EVENT_FORMATTER'


def to_camel_case(name):
    return ''.join([p.capitalize() for p in name.split('_')])


def fix_field_name(name):
    replace_map = {
        'errno': 'error_code',
        'sa_handler': 'sig_handler',
        'sa_flags': 'sig_flags'
    }
    if name in replace_map:
        name = replace_map[name]
    return str.lower(name)


def ensure_dir_exists(file_path):
    file_dir = os.path.dirname(file_path)
    if not os.path.exists(file_dir):
        os.mkdir(file_dir)


class EventParserCodeGenerator(FtraceEventCodeGenerator):
    def __init__(self, events_dir, allow_list):
        super().__init__(events_dir, allow_list)

    def parser_file_path(self, category):
        file_name = 'ftrace_{}_event_parser.cpp'.format(category)
        return os.path.join(self.output_dir, file_name)

    def generate_code(self):
        generated_cpp_sources = []

        for event in self.target_event_formats:
            type_name = '{}/{}'.format(event.category, event.name)
            logger.info('ftrace_events: "{}"'.format(type_name))

        # generate sub event parser code
        for category in self.grouped_event_formats:
            parser_src_file = self.parser_file_path(category)
            generated_cpp_sources.append(parser_src_file)

            logger.info('Generate {} ...'.format(parser_src_file))
            ensure_dir_exists(parser_src_file)

            with open(parser_src_file, 'w', encoding='utf-8') as f:
                f.write(CPP_COPYRIGHT_HEADER)
                f.write('#include "sub_event_parser.h"\n')
                f.write('\n')
                f.write("FTRACE_NS_BEGIN\n")
                f.write("namespace {\n")
                self.generate_parse_functions(category, f)
                f.write("} // namespace\n")
                f.write("FTRACE_NS_END\n")
                f.write('\n')

        # generate .gni
        generated_cpp_gni = os.path.join(self.output_dir, AUTO_GENERATED_GNI)
        logger.info('Generate {} ...'.format(generated_cpp_gni))
        with open(generated_cpp_gni, 'w', encoding='utf-8') as f:
            f.write(GN_COPYRIGHT_HEADER)
            f.write('\n')
            f.write('auto_generated_cpp_sources = [\n')
            for path in generated_cpp_sources:
                src = '{}'.format(os.path.basename(path))
                f.write('  "{}",\n'.format(src))
            f.write(']\n')

    def generate_parse_functions(self, category, f):
        count = 0
        for event in self.grouped_event_formats[category]:
            count += 1
            if count > 1:
                f.write('\n')
            f.write('{}({},\n'.format(PARSE_REGISTER_MACRO, event.name))
            f.write('[] {} {{\n'.format(PARSE_FUNCTION_ARGS))
            f.write('    int i = 0;\n')
            f.write('    auto msg = ftraceEvent.mutable_{}_format();\n'.format(
                str.lower(event.name)))
            for i in range(len(event.remain_fields)):
                self.generate_parse_field_lines(event, f, i)
            f.write("});\n")

    @staticmethod
    def generate_parse_field_lines(event, f, i):
        field_info = event.remain_fields[i]
        field_name = fix_field_name(field_info.name)
        type_info = field_info.to_proto_type()
        parse_func = None
        if type_info.tid == ProtoType.STRING:
            parse_func = 'ParseStrField'
        elif type_info.tid == ProtoType.INTEGER:
            assert type_info.size in [4, 8]
            c_type = None
            if type_info.size == 4:
                c_type = 'int32_t' if type_info.signed else 'uint32_t'
            elif type_info.size == 8:
                c_type = 'int64_t' if type_info.signed else 'uint64_t'
            parse_func = 'ParseIntField<{}>'.format(c_type)
        else:
            logger.warning('WARNING: unkown proto type:{} {}'.format(
                event.name, field_name))
        assert parse_func
        f.write('    msg->set_{}(FtraceFieldParser::'.format(field_name))
        f.write('{}(format.fields, i++, data, size));\n'.format(parse_func))


class EventFormatterCodeGenerator(FtraceEventCodeGenerator):
    def __init__(self, events_dir, allow_list):
        super().__init__(events_dir, allow_list)

    def formatter_file_path(self, category):
        file_name = 'ftrace_{}_event_formatter.cpp'.format(category)
        return os.path.join(self.output_dir, file_name)

    def generate_code(self):
        generated_cpp_sources = []

        # generate sub event parser code
        for category in self.grouped_event_formats:
            formatter_src_file = self.formatter_file_path(category)
            generated_cpp_sources.append(formatter_src_file)

            logger.info('Generate {} ...'.format(formatter_src_file))
            ensure_dir_exists(formatter_src_file)

            with open(formatter_src_file, 'w', encoding='utf-8') as f:
                f.write(CPP_COPYRIGHT_HEADER)
                f.write('#include "event_formatter.h"\n')
                f.write('#include <sstream>\n')
                f.write('\n')
                f.write("FTRACE_NS_BEGIN\n")
                f.write("namespace {\n")
                self.generate_format_functions(category, f)
                f.write("} // namespace\n")
                f.write("FTRACE_NS_END\n")
                f.write('\n')

        # generate .gni
        generated_cpp_gni = os.path.join(self.output_dir, AUTO_GENERATED_GNI)
        logger.info('Generate {} ...'.format(generated_cpp_gni))
        with open(generated_cpp_gni, 'w', encoding='utf-8') as f:
            f.write(GN_COPYRIGHT_HEADER)
            f.write('\n')
            f.write('auto_generated_cpp_sources = [\n')
            for path in generated_cpp_sources:
                src = '{}'.format(os.path.basename(path))
                f.write('  "{}",\n'.format(src))
            f.write(']\n')

    def generate_format_functions(self, category, f):
        count = 0
        for event in self.grouped_event_formats[category]:
            count += 1
            if count > 1:
                f.write('\n')
            f.write('{}({},\n'.format(FORMAT_REGISTER_MACRO, event.name))
            f.write('[] {} {{\n'.format(CHECK_FUNCTION_ARGS, ))
            f.write('    return event.has_{}_format();'.format(
                str.lower(event.name)))
            f.write('},')  # end of check function
            f.write('[] {} {{\n'.format(FORMAT_FUNCTION_ARGS))
            f.write('    auto msg = event.{}_format();\n'.format(
                str.lower(event.name)))
            f.write("    std::stringstream sout;\n")
            f.write('    sout << "{}:";\n'.format(event.name))
            for field_info in event.remain_fields:
                field_name = fix_field_name(field_info.name)
                f.write('    sout << " {}=" << msg.{}();\n'.format(field_name,
                                                                   field_name))
            f.write("    return sout.str();\n")
            f.write("});\n")  # end of format function


def main():
    parser = argparse.ArgumentParser(
        description='FTrace C++ code generator.')
    parser.add_argument('-a', dest='allow_list', required=True, type=str,
                        help='event allow list file path')
    parser.add_argument('-e', dest='events_dir', required=True, type=str,
                        help='event formats directory')
    parser.add_argument('-p', dest='parser_out', required=False, type=str,
                        help='parser code output directory')
    parser.add_argument('-f', dest='formatter_out', required=False, type=str,
                        help='formaater code output directory')

    args = parser.parse_args(sys.argv[1:])
    allow_list = args.allow_list
    events_dir = args.events_dir
    parser_out = args.parser_out
    formatter_out = args.formatter_out

    # check arguments
    if not os.path.isfile(allow_list):
        parser.print_usage()
        exit(1)
    if not os.path.isdir(events_dir):
        parser.print_usage()
        exit(2)

    if parser_out:
        if not os.path.isdir(parser_out):
            parser.print_usage()
            exit(3)
        parser_gen = EventParserCodeGenerator(events_dir, allow_list)
        parser_gen.generate(os.path.join(parser_out))

    if formatter_out:
        if not os.path.isdir(formatter_out):
            parser.print_usage()
            exit(4)
        fmtter_gen = EventFormatterCodeGenerator(events_dir, allow_list)
        fmtter_gen.generate(formatter_out)


if __name__ == '__main__':
    main()
